#include "ws2812_mulch.h"
#include "main.h"

uint8_t led_driver_bitstream[SEND_BIT_SEND_SIZE] = {0};

uint8_t PIN_ALL_HIGH = 0xFF;
uint8_t PIN_ALL_LOW = 0x00;
uint8_t led_send_done = 1;

static volatile uint16_t timer_overflows = 0;
uint16_t g_led_chn_count = BUF_SIZE_PER_CHAN;

const uint32_t TIME_UNIT_FREQ_HZ = 800000U;
const uint16_t TIMER_RELOAD = 30 - 1; // 1250 / 30 = 41.666ns

const uint16_t PWM_PULSE_WIDTH_T0H = 7;  //  (7 + 1) * 41.666 = 333.333ns
const uint16_t PWM_PULSE_WIDTH_T1H = 20; //  (20 + 1) * 41.666 = 875ns

void tim2_config(void)
{
    LL_TIM_InitTypeDef timer_initpara = {0};
    LL_TIM_OC_InitTypeDef timer_ocpara = {0};

    /* enable timer2 clock */
    LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM2);
    LL_TIM_DeInit(TIM2);

    /* timer base configure */
    timer_initpara.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
    timer_initpara.CounterMode = LL_TIM_COUNTERMODE_UP;
    timer_initpara.Prescaler = (SystemCoreClock / (TIME_UNIT_FREQ_HZ * (TIMER_RELOAD + 1))) - 1;
    timer_initpara.Autoreload = TIMER_RELOAD;
    timer_initpara.RepetitionCounter = 0;
    LL_TIM_Init(TIM2, &timer_initpara);
    LL_TIM_DisableARRPreload(TIM2);

    /* timer oc configure: channel1 T0H */ 
    timer_ocpara.OCMode = LL_TIM_OCMODE_FROZEN;
    timer_ocpara.OCState = LL_TIM_OCSTATE_DISABLE;
    timer_ocpara.CompareValue = PWM_PULSE_WIDTH_T0H;
    timer_ocpara.OCPolarity = LL_TIM_OCPOLARITY_HIGH;
    timer_ocpara.OCIdleState = LL_TIM_OCIDLESTATE_HIGH;
    LL_TIM_OC_Init(TIM2, LL_TIM_CHANNEL_CH1, &timer_ocpara);
    LL_TIM_OC_DisablePreload(TIM2, LL_TIM_CHANNEL_CH1);

    /* timer oc configure: channel2 T1H */
    timer_ocpara.OCMode = LL_TIM_OCMODE_FROZEN;
    timer_ocpara.OCState = LL_TIM_OCSTATE_DISABLE;
    timer_ocpara.CompareValue = PWM_PULSE_WIDTH_T1H;
    timer_ocpara.OCPolarity = LL_TIM_OCPOLARITY_HIGH;
    timer_ocpara.OCIdleState = LL_TIM_OCIDLESTATE_HIGH;
    LL_TIM_OC_Init(TIM2, LL_TIM_CHANNEL_CH2, &timer_ocpara);
    LL_TIM_OC_DisablePreload(TIM2, LL_TIM_CHANNEL_CH2);
    
    LL_TIM_EnableIT_UPDATE(TIM2);
    // LL_TIM_EnableCounter(TIM2);   //enable timer
    
    /* interrupt config */
    NVIC_EnableIRQ(TIM2_IRQn);
    NVIC_SetPriority(TIM2_IRQn, 0);
}

void dma_config(void)
{
    LL_DMA_InitTypeDef dma_initpara = {0};

    /* enable dma1 clock */
    LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);

    /* DMA1 CH2 timer2 update*/
    LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_2);
    dma_initpara.PeriphOrM2MSrcAddress = (uint32_t)&GPIOB->ODR;
    dma_initpara.MemoryOrM2MDstAddress = (uint32_t)&PIN_ALL_HIGH;
    dma_initpara.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
    dma_initpara.Mode = LL_DMA_MODE_NORMAL;
    dma_initpara.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
    dma_initpara.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_NOINCREMENT;
    dma_initpara.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_HALFWORD;
    dma_initpara.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE;
    dma_initpara.NbData = 0;
    dma_initpara.Priority = LL_DMA_PRIORITY_HIGH;
    LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_initpara);

    /* DMA1 CH5 timer2 cc1 */
    LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_5);
    dma_initpara.PeriphOrM2MSrcAddress = (uint32_t)&GPIOB->ODR;
    dma_initpara.MemoryOrM2MDstAddress = (uint32_t)&led_driver_bitstream;
    dma_initpara.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
    dma_initpara.Mode = LL_DMA_MODE_NORMAL;
    dma_initpara.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
    dma_initpara.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
    dma_initpara.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_HALFWORD;
    dma_initpara.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE;
    dma_initpara.NbData = 0;
    dma_initpara.Priority = LL_DMA_PRIORITY_HIGH;
    LL_DMA_Init(DMA1, LL_DMA_CHANNEL_5, &dma_initpara);

    /* DMA1 CH7 timer2 cc2*/
    LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_7);
    dma_initpara.PeriphOrM2MSrcAddress = (uint32_t)&GPIOB->ODR;
    dma_initpara.MemoryOrM2MDstAddress = (uint32_t)&PIN_ALL_LOW;
    dma_initpara.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
    dma_initpara.Mode = LL_DMA_MODE_NORMAL;
    dma_initpara.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
    dma_initpara.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_NOINCREMENT;
    dma_initpara.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_HALFWORD;
    dma_initpara.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE;
    dma_initpara.NbData = 0;
    dma_initpara.Priority = LL_DMA_PRIORITY_HIGH;
    LL_DMA_Init(DMA1, LL_DMA_CHANNEL_7, &dma_initpara);

    /* IMPORTANT: dma channel map */
    LL_SYSCFG_SetDMARemap(DMA1, LL_DMA_CHANNEL_2, LL_SYSCFG_DMA_MAP_TIM2_UP);    
    LL_SYSCFG_SetDMARemap(DMA1, LL_DMA_CHANNEL_5, LL_SYSCFG_DMA_MAP_TIM2_CH1);    
    LL_SYSCFG_SetDMARemap(DMA1, LL_DMA_CHANNEL_7, LL_SYSCFG_DMA_MAP_TIM2_CH2);    

    /* enable DMA1 Channel7 transfer complete interrupt */
    LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_7);

    /* interrupt config */
    NVIC_EnableIRQ(DMA1_Channel7_IRQn);
    NVIC_SetPriority(DMA1_Channel7_IRQn, 0);
}

void gpio_config(void)
{
    LL_GPIO_InitTypeDef gpio_initpara = {0};

    LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOB);

    gpio_initpara.Pin = 0xff; // 0~8
    gpio_initpara.Mode = LL_GPIO_MODE_OUTPUT;
    gpio_initpara.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
    gpio_initpara.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
    gpio_initpara.Pull = LL_GPIO_PULL_NO;

    LL_GPIO_Init(GPIOB, &gpio_initpara);
}

int ws2812_init(void)
{
    gpio_config();
    tim2_config();
    dma_config();
    return 0;
}

void ws2812_send_data(uint32_t buffersize)
{
    // transmission complete flag, indicate that transmission is taking place
    led_send_done = 0;

    // clear all relevant DMA flags
    // LL_DMA_ClearFlag_GI2(DMA1); LL_DMA_ClearFlag_TC2(DMA1); LL_DMA_ClearFlag_HT2(DMA1); LL_DMA_ClearFlag_TE2(DMA1);
    // LL_DMA_ClearFlag_GI5(DMA1); LL_DMA_ClearFlag_TC5(DMA1); LL_DMA_ClearFlag_HT5(DMA1); LL_DMA_ClearFlag_TE5(DMA1);
    // LL_DMA_ClearFlag_GI7(DMA1); LL_DMA_ClearFlag_HT7(DMA1); LL_DMA_ClearFlag_TE7(DMA1);
    WRITE_REG(DMA1->IFCR, LL_DMA_IFCR_CGIF2 | LL_DMA_IFCR_CTCIF2 | LL_DMA_IFCR_CHTIF2 | LL_DMA_IFCR_CTEIF2);
    WRITE_REG(DMA1->IFCR, LL_DMA_IFCR_CGIF5 | LL_DMA_IFCR_CTCIF5 | LL_DMA_IFCR_CHTIF5 | LL_DMA_IFCR_CTEIF5);
    WRITE_REG(DMA1->IFCR, LL_DMA_IFCR_CGIF7 | LL_DMA_IFCR_CHTIF7 | LL_DMA_IFCR_CTEIF7);

    // configure the number of bytes to be transferred by the DMA controller
    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, buffersize);
    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_5, buffersize);
    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_7, buffersize);

    // clear all TIM2 flags
    TIM2->SR = 0;

    // enable the corresponding DMA channels
    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2);
    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_5);
    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_7);

    // IMPORTANT: enable the TIM2 DMA requests AFTER enabling the DMA channels!
    LL_TIM_EnableDMAReq_UPDATE(TIM2);
    LL_TIM_EnableDMAReq_CC1(TIM2);
    LL_TIM_EnableDMAReq_CC2(TIM2);

    // preload counter with 29 so TIM2 generates UEV directly to start DMA transfer
    LL_TIM_SetCounter(TIM2, TIMER_RELOAD);

    // start TIM2
    LL_TIM_EnableCounter(TIM2);
}

void TIM2_IRQHandler(void)
{
    // Clear TIM2 Interrupt Flag
    LL_TIM_ClearFlag_UPDATE(TIM2);

    /* check if certain number of overflows has occured yet
     * this ISR is used to guarantee a 50us dead time on the data lines
     * before another frame is transmitted */

    if (timer_overflows < g_led_chn_count)
    {
        // count the number of occured overflows
        timer_overflows++;
    }
    else
    {
        GPIOB->ODR = PIN_ALL_LOW;
        // clear the number of overflows
        timer_overflows = 0;
        // stop TIM2 now because dead period has been reached
        LL_TIM_DisableCounter(TIM2);
        /* disable the TIM2 Update interrupt again
         * so it doesn't occur while transmitting data */
        LL_TIM_DisableIT_UPDATE(TIM2);
        // finally indicate that the data frame has been transmitted
        led_send_done = 1;
    }
}

void DMA1_Channel7_IRQHandler(void)
{
    // clear DMA7 transfer complete interrupt flag
    LL_DMA_ClearFlag_TC7(DMA1);

    // enable TIM2 Update interrupt to append 50us dead period
    LL_TIM_EnableIT_UPDATE(TIM2);

    // disable the DMA channels
    LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2);
    LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_5);
    LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_7);

    // IMPORTANT: disable the DMA requests, too!
    LL_TIM_DisableDMAReq_UPDATE(TIM2);
    LL_TIM_DisableDMAReq_CC1(TIM2);
    LL_TIM_DisableDMAReq_CC2(TIM2);
}
